package top.healthylife.docx4jhelper.utils;

import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import top.healthylife.docx4jhelper.annotation.Docx4j;
import top.healthylife.docx4jhelper.annotation.Docx4jField;
import top.healthylife.docx4jhelper.annotation.Docx4jTbl;
import top.healthylife.docx4jhelper.annotation.Docx4jTblField;
import top.healthylife.docx4jhelper.base.Docx4jBaseTpl;
import top.healthylife.docx4jhelper.enums.Docx4jNodeType;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.docx4j.Docx4J;
import org.docx4j.TraversalUtil;
import org.docx4j.convert.out.FOSettings;
import org.docx4j.finders.RangeFinder;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFont;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.Color;
import org.docx4j.wml.*;

import java.awt.*;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

import static org.docx4j.Docx4J.createFOSettings;

/**
 * Word-Pdf统一工具类。
 * 可直接将Word内容进行替换， 或将word恩为pdf文档。工具使用到了docx4j2pdf工具包。 可替换不同的模板
 * 所有的模板都将使用到Redis， 将Inputstram存放到Redis中，和内存中进行计算， 需要使用到的时候再拿出来。
 * map中存储的key 和redis中存储的相同 都是带前缀的,返回的都是文件的MD5
 *
 * @author guojing
 */

@Slf4j
public class WordFileUtil {

    public static Map<String, String> templateMap = new HashMap<>();

    ///**
    // * 采用书签的方式进行word 变量替换
    // *
    // * @param load   文档包 对象
    // * @param map    变量map
    // * @param bmList 书签list
    // */
    //@Deprecated
    //public static void replaceContentByBookmark(WordprocessingMLPackage load, Map<String, Object> map, List<String> bmList) {
    //    try {
    //        log.info("准备变量替换");
    //        long l = System.currentTimeMillis();
    //        // 提取正文
    //        MainDocumentPart mainDocumentPart = load.getMainDocumentPart();
    //        Document doc = mainDocumentPart.getContents();
    //        Body body = doc.getBody();
    //
    //        // 获取段落
    //        List<Object> paragraphs = body.getContent();
    //
    //        // 提取书签并获取书签的游标
    //        RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
    //        new TraversalUtil(paragraphs, rt);
    //        List<CTBookmark> ctBookmarks = rt.getStarts();
    //        //// TODO: 2022/1/17
    //        boolean replaceBookmarksByTbl = false;
    //        boolean wantAddTbl = map.keySet().stream().anyMatch(e -> e.startsWith("table"));
    //        if (!ctBookmarks.isEmpty()) {
    //            // 存在书签,遍历书签
    //            for (CTBookmark bm : ctBookmarks) {
    //                // 替换文本内容
    //                for (String bmName : bmList) {
    //                    if (bm.getName().equals(bmName) && !bm.getName().startsWith("table")) {
    //                        Tool.replaceText(
    //                                bm, map.get(bm.getName()));
    //                    }
    //                    //书签中包含表格,则在书签处替换表格
    //                    if (bm.getName().startsWith("table") && map.containsKey(bm.getName())) {
    //                        replaceBookmarksByTbl = true;
    //                        Tool.addTbl(mainDocumentPart, bm, (Tbl) map.get(bm.getName()));
    //                    }
    //
    //                }
    //            }
    //            long l1 = System.currentTimeMillis();
    //            log.info("变量替换完成,耗时:{}毫秒", l1 - l);
    //        }
    //
    //        if (!replaceBookmarksByTbl && wantAddTbl) {
    //            //不存在书签,但是map中有table对象,想要创建table,默认在文档尾创建
    //            processTable(load, map);
    //        } else {
    //
    //        }
    //        log.info("变量替换完成");
    //    } catch (Exception e) {
    //        log.info("书签变量替换异常:{}", e.getMessage());
    //        throw new RuntimeException("变量替换异常");
    //    }
    //}

    //@Deprecated
    //public static void processTable(WordprocessingMLPackage load, Map<String, Object> map) {
    //    int count = containsKey(map, "table");
    //    if (count > 0) {
    //        //新增表格
    //        log.info("变量包含表格,数量:{}", count);
    //        //约定 表格1的 key table:title:1,表格2的key table:title:2,...
    //        for (int i = 1; i <= count; i++) {
    //            long l2 = System.currentTimeMillis();
    //            String dataKey = "table:" + i;
    //            TableEmbedContainer tableEmbedContainer = null;
    //            List<LinkedHashMap<String, Object>> dataList = null;
    //            try {
    //                Object o = map.get(dataKey);
    //                tableEmbedContainer = BeanUtil.copyProperties(o, TableEmbedContainer.class);
    //            } catch (Exception exception) {
    //                throw new RuntimeException("请使用标准的表格对象封装数据");
    //            }
    //            dataList = tableEmbedContainer.getDataList();
    //            if (ObjectUtil.isEmpty(dataList)) {
    //                continue;
    //            }
    //            //获取标题
    //            List<String> titleList = tableEmbedContainer.getTitles();
    //            Tbl table = createTable(load, titleList, dataList);
    //            addTable(load, table);
    //            log.info("表格{}创建完成,耗时{}毫秒", i, System.currentTimeMillis() - l2);
    //        }
    //    }
    //}


    /**
     * 转换为pdf格式
     * 将工具包转为pdf文件。自定义输出流对象
     *
     * @param outputStream 输出流
     * @param template     模板对象
     */
    @SneakyThrows
    public static void convertDocx2Pdf(WordprocessingMLPackage template, OutputStream outputStream) {
        //docx4j docx转pdf
        FOSettings foSettings = createFOSettings();
        foSettings.setWmlPackage(template);
        Docx4J.toFO(foSettings, outputStream, Docx4J.FLAG_EXPORT_PREFER_NONXSL);
    }

    /**
     * 转换为docx格式
     * 将工具包转为pdf文件。自定义输出流对象
     *
     * @param outputStream 输出流
     * @param template     模板对象
     */
    @SneakyThrows
    public static void convertDocx2Docx(WordprocessingMLPackage template, OutputStream outputStream) {
        //docx4j docx转pdf
        FOSettings foSettings = createFOSettings();
        foSettings.setWmlPackage(template);
        //直接保存为word
        template.save(outputStream);
    }

    static Mapper fontMapper = new IdentityPlusMapper();

    static Map<String, PhysicalFont> systemFontMapper = new HashMap<>();

    static {
        TimeInterval timeInterval = new TimeInterval();
        Font[] allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
        for (Font allFont : allFonts) {
            String psName = allFont.getPSName();
            systemFontMapper.put(allFont.getFamily(), PhysicalFonts.get(psName));
        }
        log.info("加载字体文件耗时:{}ms", timeInterval.intervalMs());
    }

    protected static void reloadFontMapper() {
        //加载系统字体。
        TimeInterval timeInterval = new TimeInterval();
        systemFontMapper.keySet().forEach(e -> {
            fontMapper.put(e, systemFontMapper.get(e));
        });

        log.info("重载字体文件耗时:{}ms", timeInterval.intervalMs());
    }

    /**
     * 初始化模板到内存中。
     *
     * @param key    生成的模板 prefix+md5
     * @param encode 文件流。
     */
    @SneakyThrows
    protected static void putTemplateMap(String key, String encode) {
        log.info("准备加载文件到内存中,key:{}", key);
        long l = System.currentTimeMillis();
        templateMap.put(key, encode);
        long l1 = System.currentTimeMillis();
        log.info("耗时:{}毫秒,内存中的模板对象个数:=>{}", l1 - l, templateMap.size());
    }

    /**
     * 采用书签的方式进行word 变量替换
     *
     * @param load     文档包 对象
     * @param bookmark 书签名
     * @param obj      替换的对象
     */
    @SneakyThrows
    public static void singleReplaceContentByBookmark(WordprocessingMLPackage load, String bookmark, Object obj) {
        List<CTBookmark> ctBookmark = findCTBookmark(bookmark, load);
        replaceMainDocumentPartByCTBookmark(load.getMainDocumentPart(), ctBookmark, false, obj);
    }

    /**
     * 采用书签的方式进行word 变量替换
     *
     * @param load 文档包 对象
     * @param map  书签名以及对应对象 的映射
     */
    public static void batchReplaceContentByBookmark(WordprocessingMLPackage load, Map<String, Object> map) {
        try {
            log.info("准备变量替换");
            long l = System.currentTimeMillis();
            Set<String> bookmarkSet = map.keySet();
            for (String bookmark : bookmarkSet) {
                Object o = map.get(bookmark);
                boolean textFlag = bookmark.startsWith("text:");
                boolean imgFlag = bookmark.startsWith("image:");
                String[] split = bookmark.split(":");
                List<CTBookmark> ctBookmark = findCTBookmark(split[1], load);
                if (textFlag) {
                    ctBookmark.forEach(e -> {
                        try {
                            Tool.replaceText(e, o);
                        } catch (Exception exception) {
                            exception.printStackTrace();
                        }
                    });

                }
                if (imgFlag) {
                    //如果图片处理失败,获取的是url地址,需要转换为行对象才行
                    Object obj;
                    if (o instanceof String) {
                        String str = (String) o;
                        obj = createP(StrUtil.isBlank(str) ? "" : str);
                    } else {
                        obj = o;
                    }
                    replaceMainDocumentPartByCTBookmark(load.getMainDocumentPart(), ctBookmark, false, obj);
                }

            }
            //// 获取段落
            //List<Object> paragraphs = body.getContent();
            //// 提取书签并获取书签的游标
            //RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
            //new TraversalUtil(paragraphs, rt);
            //List<CTBookmark> ctBookmarks = rt.getStarts();
            //if (!ctBookmarks.isEmpty()) {
            //    // 存在书签,遍历书签
            //    for (CTBookmark bm : ctBookmarks) {
            //        // 替换文本内容
            //        for (String bmName : bookmarkSet) {
            //            boolean textFlag = bmName.startsWith("text:");
            //            boolean imgFlag = bmName.startsWith("image:");
            //            String[] split = bmName.split(":");
            //
            //        }
            //    }
            //    long l1 = System.currentTimeMillis();
            //    log.info("变量替换完成,耗时:{}毫秒", l1 - l);
            //}
            log.info("变量替换完成");
        } catch (Exception e) {
            log.info("书签变量替换异常:{}", e.getMessage());
            throw new RuntimeException("变量替换异常");
        }
    }


    public static void addTable(WordprocessingMLPackage mlPackage, Tbl tbl) {
        //处理下行数据,在渲染数据时,key无用
        MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();
        mainDocumentPart.addObject(tbl);
        mainDocumentPart.addParagraphOfText("\n");
    }

    @SneakyThrows
    /**
     * 创建表格
     *
     * @param mlPackage mlPackage
     * @param titleList 标题
     * @param dataList  数据
     */
    public static Tbl createTable(WordprocessingMLPackage mlPackage, List<String> titleList, List<LinkedHashMap<String, Object>> dataList) {
        ArrayList<LinkedHashMap<Integer, Object>> afterHandlerList = new ArrayList<>(dataList.size());
        for (LinkedHashMap<String, Object> dataRow : dataList) {
            LinkedHashMap<Integer, Object> afterHandlerData = new LinkedHashMap<>();
            int i = 0;
            for (String key : dataRow.keySet()) {
                afterHandlerData.put(i++, dataRow.get(key));
            }
            afterHandlerList.add(afterHandlerData);
        }
        //处理行列
        MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();
        int length = titleList.size();
        Tbl table = Docx4jUtil.createTable(mlPackage, dataList.size() + 1, length);
        Docx4jUtil.setTableCellMargin(table, "200", "100", "200", "100");
        List<Tr> tblAllTr = Docx4jUtil.getTblAllTr(table);
        //创建表格
        for (int i = 0; i < tblAllTr.size(); i++) {
            List<Tc> trAllCell = Docx4jUtil.getTrAllCell(tblAllTr.get(i));
            for (int j = 0; j < trAllCell.size(); j++) {
                //插入内容
                String content;
                //第一行插入表头
                if (i == 0) {
                    content = titleList.get(j);
                } else {
                    //插入数据
                    LinkedHashMap<Integer, Object> afterHandlerData = afterHandlerList.get(i - 1);
                    Object o = afterHandlerData.get(j);
                    content = o == null ? "" : o.toString();
                    content = contentAutoAdjust(mlPackage, content, length);
                }
                Tc tc = trAllCell.get(j);
                Docx4jUtil.setTcContent(tc, content, "黑体", "Times New Roman");
                TblWidth tblWidth = new TblWidth();
                tblWidth.setW(new BigInteger("250"));
                tc.setTcPr(new TcPr());
                tc.getTcPr().setTcW(tblWidth);
            }
        }
        return table;
    }

    public static int containsKey(Map map, String key) {
        int count = 0;
        for (Object o : map.keySet()) {
            if (o.toString().startsWith(key)) {
                count++;
            }
        }
        return count;
    }

    /**
     * 内容超过指定长度换行
     *
     * @param content 内容
     * @return
     */
    @SneakyThrows
    public static String contentAutoAdjust(WordprocessingMLPackage wordPackage, String content, int colNum) {
        //获取文档的可用宽度 A4可写大约是9000,字体五号,能写大约42个汉字,84个数字,90个字母
        //即五号字体下: 1个字母对应100 单位
        //            1个汉字对应214 单位
        //            1个数字对应107 单位
        int writableWidth = Docx4jUtil.getWritableWidth(wordPackage);
        //列宽
        double colWidth = new Double(String.valueOf(writableWidth / colNum));
        double contentWidth = getContentWidth(content);
        //占据单元格80%的时候考虑换行
        return doBreakLine(contentWidth, colWidth, content, new StringBuilder());
    }

    /**
     * 内容超过指定长度换行,本地测试用
     *
     * @param content 内容
     * @return
     */
    @SneakyThrows
    public static String contentAutoAdjust(String content) {
        //获取文档的可用宽度 A4可写大约是9000,字体五号,能写大约42个汉字,84个数字,90个字母
        //即五号字体下: 1个字母对应100 单位
        //            1个汉字对应214 单位
        //            1个数字对应107 单位
        //列宽
        double colWidth = 1500;
        double contentWidth = getContentWidth(content);
        //占据单元格80%的时候考虑换行
        return doBreakLine(contentWidth, colWidth, content, new StringBuilder());
    }

    public static String doBreakLine(double contentWidthDouble, double colWidthDouble, String content, StringBuilder result) {
        if (contentWidthDouble / colWidthDouble <= 0.8) {
            return result.append(content).toString();
        }
        //占据单元格80%的时候考虑换行
        if (contentWidthDouble / colWidthDouble > 0.8) {
            //获取换行的字符下标
            double v = colWidthDouble * 0.8;
            int index = getContentWidth(content, v);
            String theSecondHalf = content.substring(index);
            String theFirstHalf = content.substring(0, index);
            result.append(theFirstHalf);
            double contentWidth = getContentWidth(theSecondHalf);
            if (contentWidthDouble / colWidthDouble > 0.8) {
                result.append("\n");
                doBreakLine(contentWidth, colWidthDouble, theSecondHalf, result);
            }
        }
        return result.toString();
    }


    public static double getContentWidth(String content) {
        if (StrUtil.isBlank(content)) {
            return 0;
        }
        double width = 0.0;
        for (int i = 0; i < content.length(); i++) {
            char c = content.charAt(i);
            if (PinyinUtil.isChinese(c)) {
                width = width + 214;
            }
            if (NumberUtil.isInteger(Character.toString(c))) {
                width = width + 107;
            }
            width = width + 100;
        }
        return width;
    }

    /**
     * 获取指定宽度下的字符下标
     *
     * @param content
     * @param width
     * @return
     */
    public static int getContentWidth(String content, double width) {
        if (StrUtil.isBlank(content)) {
            return 0;
        }
        double charWidth = 0.0;
        for (int i = 0; i < content.length(); i++) {
            Character c = content.charAt(i);
            if (PinyinUtil.isChinese(c)) {
                charWidth = charWidth + 214;
            } else if (NumberUtil.isInteger(c.toString())) {
                charWidth = charWidth + 107;
            } else {
                charWidth = charWidth + 100;
            }
            if (charWidth > width) {
                return i - 1;
            }
        }
        return content.length();
    }

    /**
     * 获取要渲染的有效字段数
     *
     * @param data 模板数据
     * @return
     */
    public static <T> List<Field> getEffectiveField(T data) {
        Class<?> aClass = data.getClass();
        boolean docx4jPresent = aClass.isAnnotationPresent(Docx4j.class);
        boolean docx4jTplPresent = aClass.isAnnotationPresent(Docx4jTbl.class);
        if (!docx4jPresent && !docx4jTplPresent) {
            throw new RuntimeException("缺少对应的注解,请使用@Docx4j注解或@Docx4jTpl注解");
        }
        if (docx4jPresent && docx4jTplPresent) {
            throw new RuntimeException("@Docx4j,@Docx4jTpl不和同时使用");
        }
        if (docx4jPresent) {
            return getEffectiveFieldOnDocx4j(data);
        }
        return getEffectiveFieldOnDocx4jTpl(data);
    }


    /**
     * 获取有效字段数
     *
     * @param data 表格数据
     * @return
     */
    public static <T> List<Field> getEffectiveFieldOnDocx4j(T data) {
        Field[] declaredFields = data.getClass().getDeclaredFields();
        List<Field> fields = Arrays.asList(declaredFields);
        List<Field> effectiveFields = new ArrayList<>();
        for (int i = 0; i < fields.size(); i++) {
            Field e = fields.get(i);
            boolean annotationPresent = e.isAnnotationPresent(Docx4jTblField.class);
            if (annotationPresent) {
                throw new RuntimeException("不期望的注解@Docx4jTblField,表格字段注解应使用@Docx4jField");
            }
            Docx4jField docx4jField = e.getAnnotation(Docx4jField.class);
            if (docx4jField != null && docx4jField.ignore()) {
                continue;
            }
            effectiveFields.add(e);
        }
        return effectiveFields;
    }


    /**
     * 获取有效字段数
     *
     * @param data 表格数据
     * @return
     */
    public static <T> List<Field> getEffectiveFieldOnDocx4jTpl(T data) {
        Field[] declaredFields = data.getClass().getDeclaredFields();
        List<Field> fields = Arrays.asList(declaredFields);
        //表格暂时不支持嵌套
        List<Field> effectiveFields = new ArrayList<>();
        for (int i = 0; i < fields.size(); i++) {
            Field e = fields.get(i);
            boolean annotationPresent = e.isAnnotationPresent(Docx4jField.class);
            if (annotationPresent) {
                throw new RuntimeException("不期望的注解@Docx4jField,表格字段注解应使用@Docx4jTblField");
            }
            Docx4jTblField docx4jTblField = e.getAnnotation(Docx4jTblField.class);
            if (docx4jTblField != null && docx4jTblField.ignore()) {
                continue;
            }
            effectiveFields.add(e);
        }
        return effectiveFields;
    }

    /**
     * 定位书签的位置
     *
     * @param bookmarkName 书签名
     * @param wordPackage  文档包
     * @return 书签对象集合
     */
    public static List<CTBookmark> findCTBookmark(String bookmarkName, WordprocessingMLPackage wordPackage) throws Docx4JException {
        //找到书签位置
        log.debug("查找书签:{}", bookmarkName);
        Document doc = wordPackage.getMainDocumentPart().getContents();
        Body body = doc.getBody();
        // 获取段落
        List<Object> paragraphs = body.getContent();
        // 提取书签并获取书签的游标
        RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
        new TraversalUtil(paragraphs, rt);
        List<CTBookmark> ctBookmarks = rt.getStarts();
        List<CTBookmark> collect = ctBookmarks.stream().filter(e -> e.getName().equals(bookmarkName)).collect(Collectors.toList());
        log.info("查找到{}个名称为{}的书签", collect.size(), bookmarkName);
        return collect;
    }

    /**
     * 根据书签替换word文档的位置
     *
     * @param mainDocumentPart 文档包
     * @param ctBookmarks      书签
     * @param obj              替换到对象
     * @param isSpacing        是否在对象前后添加一行空行
     */
    public static void replaceMainDocumentPartByCTBookmark(MainDocumentPart mainDocumentPart, List<CTBookmark> ctBookmarks, Boolean isSpacing, Object obj) {
        if (ObjectUtil.isEmpty(ctBookmarks)) {
            return;
        }
        List<Object> content = mainDocumentPart.getContent();
        int count = 0;
        for (int i = 0; i < ctBookmarks.size(); i++) {
            CTBookmark e = ctBookmarks.get(i);
            Object parent = e.getParent();
            for (int j = 0; j < content.size(); j++) {
                if (content.get(j).equals(parent)) {
                    count++;
                    if (isSpacing) {
                        // fixme: 2022/1/24   有问题
                        log.info("对象前后追加一行空行");
                        //生成两个空行
                        P p = createP("");
                        Object[] insert = {p, obj, p};
                        content = insert(content, j, insert);

                    } else {
                        content.set(j, obj);
                    }
                }
            }
        }
        log.debug("书签{},共计{}个,已找到并替换{}处", ctBookmarks.get(0).getName(), ctBookmarks.size(), count);
    }

    /**
     * 将新元素插入到到已有数组中的某个位置<br>
     * 添加新元素会生成一个新的数组，不影响原数组<br>
     * 如果插入位置为为负数，从原数组从后向前计数，若大于原数组长度，则空白处用null填充
     *
     * @param <E>         集合元素类型
     * @param collection  已有集合
     * @param index       插入位置，此位置为对应此位置元素之前的空档
     * @param newElements 新元素
     * @return 新数组
     * @since 4.0.8
     */
    @SuppressWarnings({"unchecked", "SuspiciousSystemArraycopy"})
    public static <E> List<E> insert(List<E> collection, int index, E... newElements) {
        List<E> newCollection = new ArrayList<>(collection.size() + newElements.length);
        //1.index= 0,即先插入新元素,再插入旧元素
        if (index == 0) {
            newCollection.addAll(Arrays.asList(newElements));
            newCollection.addAll(collection);
        } else if (index > 0 && index < collection.size()) {
            //2. 0<index<collection.size()  即在0到index-1的位置插入旧元素,插入个数为index-1,然后插入新元素,最后插入剩下的旧元素
            for (int i = 0; i < collection.size() + newElements.length - 1; i++) {
                if (i <= index - 1) {
                    newCollection.add(collection.get(i));
                } else if (i >= index && i <= index + newElements.length - 1) {
                    newCollection.add(i, newElements[i - index]);
                } else {
                    newCollection.add(collection.get(i - newElements.length));
                }
            }
        } else {
            //3. index=collection.size() 在元素末尾追加新元素
            newCollection.addAll(collection);
            newCollection.addAll(Arrays.asList(newElements));
        }
        return newCollection;
    }

    /**
     * 创建空行
     *
     * @param
     * @return
     */
    public static P createP() {
        return createP("");
    }

    /**
     * 创建段落
     *
     * @param content 段落内容
     * @return P
     */
    public static P createP(String content) {
        ObjectFactory factory = Context.getWmlObjectFactory();
        P p = factory.createP();
        R run = factory.createR();
        Text t = factory.createText();
        t.setValue(content);
        run.getContent().add(t);
        p.getContent().add(run);
        return p;
    }

    public static void setPAlign(P p, JcEnumeration jcEnumeration) {
        PPr pPr = p.getPPr();
        if (pPr == null) {
            pPr = Context.getWmlObjectFactory().createPPr();
        }
        Jc jc = new Jc();
        jc.setVal(jcEnumeration);
        pPr.setJc(jc);
    }

    /**
     * fixme 有问题,导致生成的word 在转换为PDF时异常,也会导致目录生成异常
     *
     * @param content
     * @return
     */
    public static P createPWithStyle(String content) {
        return createPWithStyle(content, "黑体", "#000000", 26, JcEnumeration.CENTER, true, false, false, false);
    }

    public static P createPWithStyle(String content, Integer fontSize) {
        return createPWithStyle(content, "黑体", "#000000", fontSize, JcEnumeration.CENTER, true, false, false, false);
    }

    /**
     * 段落样式  宋体,#abcdefg,20,center,/B,/I,/D,/U
     *
     * @param content         内容
     * @param fontName        字体
     * @param color           颜色
     * @param fontSize        字号
     * @param align           对齐方式
     * @param isBold          加粗
     * @param isItalic        倾斜
     * @param isStrikethrough 删除线
     * @param isUnderline     下划线
     * @return P
     */
    public static P createPWithStyle(String content, String fontName, String color, Integer fontSize, JcEnumeration align, boolean isBold, boolean isItalic, boolean isStrikethrough, boolean isUnderline) {
        ObjectFactory factory = Context.getWmlObjectFactory();

        //创建段落
        P p = factory.createP();

        //文本渲染器
        R run = factory.createR();
        //创建文本
        Text t = factory.createText();
        t.setValue(content);

        //挂在文本
        run.getContent().add(t);

        //渲染属性配置
        RPr rpr = factory.createRPr();

        RFonts font = new RFonts();
        //设置字体
        font.setAscii(fontName);
        font.setEastAsia(fontName);//经测试发现这个设置生效
        rpr.setRFonts(font);

        //设置颜色
        Color colorConfig = new Color();
        colorConfig.setVal(color);
        rpr.setColor(colorConfig);

        //设置字体大小
        HpsMeasure fontSizeConfig = new HpsMeasure();
        //这里测试发现,word显示的字体大小总是 传入的值的一半
        fontSizeConfig.setVal(new BigInteger(StrUtil.toString(fontSize * 2)));
        rpr.setSzCs(fontSizeConfig);
        rpr.setSz(fontSizeConfig);

        //设置粗体 /B
        BooleanDefaultTrue bold = factory.createBooleanDefaultTrue();
        bold.setVal(isBold);
        rpr.setB(bold);

        //设置斜体 /I
        BooleanDefaultTrue ltalic = new BooleanDefaultTrue();
        ltalic.setVal(isItalic);
        rpr.setI(ltalic);

        //设置删除线  /D
        BooleanDefaultTrue deleteLine = new BooleanDefaultTrue();
        deleteLine.setVal(isStrikethrough);
        rpr.setStrike(deleteLine);

        //设置下划线 /U
        U u = factory.createU();
        if (isUnderline) {
            u.setVal(UnderlineEnumeration.SINGLE);
            //u.setVal(UnderlineEnumeration.DOUBLE);//双下划线
            //u.setVal(UnderlineEnumeration.DASH);//虚线
            //u.setVal(UnderlineEnumeration.WAVE);//波浪线
        }
        rpr.setU(u);

        //设置渲染器的渲染属性
        run.setRPr(rpr);

        //设置段落居中样式
        PPr ppr = new PPr();
        Jc jc = new Jc();
        jc.setVal(align);
        ppr.setJc(jc);

        //给段落挂载样式
        p.setPPr(ppr);
        p.getContent().add(run);
        return p;

    }


    /**
     * docx4j 变量解析处理
     */
    @SneakyThrows
    public static WordprocessingMLPackage docx4jVariableParse(WordprocessingMLPackage load, Docx4jBaseTpl docx4jBaseTpl) {
        if (ObjectUtil.isEmpty(docx4jBaseTpl)) {
            throw new RuntimeException("docx4j模板对象不能为空");
        }
        log.info("变量替换开始");
        Map<Class<?>, List<?>> tableDataMap = docx4jBaseTpl.getTableDataMap();
        if (tableDataMap != null) {
            Set<Class<?>> set = tableDataMap.keySet();
            for (Class<?> aClass : set) {
                Docx4jTbl annotation = aClass.getAnnotation(Docx4jTbl.class);
                if (annotation != null) {
                    String tableName = annotation.tableName();
                    String bookmarkName = ObjectUtil.isEmpty(annotation.bookmarkName())
                            //小驼峰
                            ? aClass.getSimpleName().substring(0, 1).toLowerCase() + aClass.getSimpleName().substring(1)
                            : annotation.bookmarkName();
                    //表格暂时不支持嵌套
                    Tbl tbl = Docx4jUtil.createTableAndInitData(load, tableDataMap.get(aClass));

                    //表格放置在指定位置
                    WordFileUtil.singleReplaceContentByBookmark(load, bookmarkName, tbl);
                }
            }
        }

        ObjectFactory factory = Context.getWmlObjectFactory();
        //变量非空 则进行变量替换
        MainDocumentPart mainDocumentPart = load.getMainDocumentPart();
        //String name = Get.name(Docx4jBaseTpl::getTableDataMap);
        //表格数据字段
        String name = "tableDataMap";
        List<Field> effectiveField = WordFileUtil.getEffectiveField(docx4jBaseTpl);
        List<Field> collect = effectiveField.stream().filter(e -> !e.getName().equals(name)).collect(Collectors.toList());
        //书签渲染用
        Map<String, Object> hashMap = new HashMap<>();
        //${}docx 原生 变量替换用
        Map<String, String> varMap = new HashMap<>();

        if (ObjectUtil.isNotEmpty(collect)) {
            collect.forEach(e -> {
                Object value;
                //如果当前字段是图片,则特殊处理,不和文字一起
                //找到正确的那个字段
                e.setAccessible(true);
                String bookmarkName = e.getName();

                Docx4jField annotation = e.getAnnotation(Docx4jField.class);
                if (annotation == null || annotation.type().equals(Docx4jNodeType.TEXT)) {
                    bookmarkName = "text:" + bookmarkName;
                }
                try {
                    value = e.get(docx4jBaseTpl);
                } catch (IllegalAccessException illegalAccessException) {
                    throw new RuntimeException("获取字段" + bookmarkName + "的值异常");
                }

                if (annotation != null && annotation.type().equals(Docx4jNodeType.IMAGE)) {
                    bookmarkName = "image:" + bookmarkName;
                    try {

                        Drawing imagePart = Docx4jUtil.createImagePart(load, StrUtil.toString(value), e.getName());
                        P p = factory.createP();
                        org.docx4j.wml.R run = factory.createR();
                        run.getContent().add(imagePart);
                        p.getContent().add(run);
                        value = p;
                    } catch (Exception ex) {
                        log.warn("获取图片失败,展示原始url,错误日志:{}", ex.getMessage());
                    }
                }
                hashMap.put(bookmarkName, value);
                boolean flag = value instanceof P;
                if (!flag) {
                    varMap.put(bookmarkName.split(":")[1], StrUtil.toString(value));
                }
            });
            WordFileUtil.batchReplaceContentByBookmark(load, hashMap);
            mainDocumentPart.variableReplace(varMap);
            log.info("变量替换完成");
        } else {
            log.info("变量map为空,无需变量替换");
        }
        return load;
    }

}
